Skip to content

Fix Shizuku double-bind cascade that orphaned virtual displays#49

Merged
Suprhimp merged 1 commit into
masterfrom
devplanningo/shizuku-auth-debug
May 16, 2026
Merged

Fix Shizuku double-bind cascade that orphaned virtual displays#49
Suprhimp merged 1 commit into
masterfrom
devplanningo/shizuku-auth-debug

Conversation

@Suprhimp
Copy link
Copy Markdown
Owner

Summary

  • VirtualDisplayManager and ShizukuSetup were both calling Shizuku.bindUserService. VDM also treated every onServiceConnected after the first as a binder death and recreated the VD — so duplicate connects spawned 6+ orphan VirtualDisplays per session and zombie :privileged processes.
  • Result: apps launched from the web app-list targeted a different display ID than the one being encoded, the browser canvas stayed empty, and the JS-side 5s timeout fired "Launch timed out, try again" while the activity appeared on the phone.

Changes

  • VirtualDisplayManager: strip the embedded Shizuku.bindUserService ServiceConnection. New attachPrivilegedService(svc: IPrivilegedService?) API receives the binder from outside. release() is now local-state only.
  • MirrorForegroundService:
    • ensureShizukuSetup() — lazy singleton creation at first browser connect; ShizukuSetup now lives for the foreground-service lifetime.
    • startReconnectObserver(setup) — singleton coroutine (reconnectJob) collects setup.serviceConnected and classifies transitions via BinderConnectionTracker. Only Reconnect recreates a VD; FirstConnect is owned by trySetupVirtualDisplay.
    • trySetupVirtualDisplay rewritten: waits for serviceConnected.first { it } with timeout, reuses long-lived setup, gated by shizukuSetupInProgress against concurrent rebuild races. Single-delivery semantics via safeResult.
    • releaseShizukuSessiontearDownVdSession — no longer releases ShizukuSetup mid-session. performCleanup cancels reconnectJob before shizukuSetup.release().
    • displayId < 0 guards on launchExternalBrowserTarget and launchSplitExternalBrowserTarget.
  • ShizukuSetup: removed dead attachPrivilegedService(svc) API (had no callers after the refactor).
  • BinderConnectionTracker (new, pure logic, 7 unit tests): state machine that distinguishes a fresh connect from a reconnect after a real disconnect. Key correctness rule: onDisconnected from New stays in New (so MutableStateFlow's initial false emission to a fresh collector doesn't misclassify the next real connect as Reconnect).

On-device verification (Samsung Z Flip 6, open state)

Before:

13:17:50.967 VD_CREATED id=26
13:17:51.051 VD_CREATED id=27
13:17:51.150 VD_CREATED id=28
13:17:51.239 VD_CREATED id=29
13:17:51.488 VD_CREATED id=30
13:17:51.981 VD_CREATED id=31

(6 VDs in 1s; 14+ zombie :privileged processes accumulated across sessions)

After:

10:09:46.694 Shizuku connection transition=FirstConnect connected=true
10:09:46.939 VD_CREATED id=64 864x720
10:09:50.458 Launched com.nhn.android.nmap/com.naver.map.LaunchActivity on virtual display 64

(1 VD per session; canvas renders the launched app in browser as expected.)

Out of scope

When Z Flip is closed, Samsung's FlipLargeCoverScreenSizeCompatMode intercepts am start --display <vd> for non-whitelisted apps (e.g. Naver Map) and reroutes them to the cover screen, bypassing the virtual display. This is per-app OS policy — Samsung Calculator launches on the Castla VD under the same code path. Not addressed in this patch; tracked separately.

Test plan

  • ./gradlew :app:testDebugUnitTest --tests "com.castla.mirror.shizuku.*" — green
  • On-device verification on Samsung Z Flip 6 (flip open): 1 VD per session, transition=FirstConnect, single Shizuku bind, app launches render in browser canvas
  • Verify on additional non-foldable devices to ensure no regression in the open-flip / non-foldable launch path

🤖 Generated with Claude Code

VirtualDisplayManager and ShizukuSetup were both calling
Shizuku.bindUserService, and VDM treated every onServiceConnected
callback after the first as a binder death — so duplicate connects
spawned 6+ orphan VDs per session and apps launched from the web
launcher targeted a different display than the one being encoded.

Consolidate bind ownership in ShizukuSetup; VDM becomes a passive
helper that mirrors the privileged service via attachPrivilegedService.
Add BinderConnectionTracker (pure logic, unit-tested) so reconnect is
only triggered after a real disconnect event.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Suprhimp Suprhimp added the patch Version bump: patch (1.2.3 → 1.2.4) label May 16, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented May 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
website Ready Ready Preview, Comment May 16, 2026 0:38am

@Suprhimp Suprhimp merged commit 6b3747f into master May 16, 2026
4 checks passed
@Suprhimp Suprhimp deleted the devplanningo/shizuku-auth-debug branch May 25, 2026 05:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch Version bump: patch (1.2.3 → 1.2.4)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant